// @tags: [
//   requires_getmore,
// ]

import "jstests/libs/query/sbe_assert_error_override.js";

import {arrayEq} from "jstests/aggregation/extras/utils.js";

const coll = db.computed_projection;
coll.drop();
const documents = [
    {_id: 0, a: 1, b: "x", c: 10, zero: 0},
    {_id: 1, a: 2, b: "y", c: 11},
    {_id: 2, a: 3, b: "z", c: 12},
    {_id: 3, x: {y: 1}},
    {_id: 4, x: {y: 2}},
    {_id: 5, x: {y: [1, 2, 3]}, v: {w: [4, 5, 6]}},
    {_id: 6, x: {y: 4}, v: {w: 4}},
    {_id: 7, x: [{y: 1}], v: [{w: 1}]},
    {_id: 8, x: [{y: 1}, {y: 2}], v: [{w: 5}, {w: 6}]},
    {_id: 9, x: [{y: 1}, {y: [1, 2, 3]}], v: [{w: 4}, {w: [4, 5, 6]}]},
    {_id: 10, z: 1},
    {_id: 11, z: 2},
    {_id: 12, z: [1, 2, 3]},
    {_id: 13, z: 3},
    {_id: 14, z: 4},
    {_id: 15, a: 10, x: 1},
    {_id: 16, a: 10, x: 10},
    {_id: 17, x: {y: [{z: 1}, {z: 2}]}},
    {_id: 18, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
    {_id: 19, i: {j: 5}, k: {l: 10}},
    {_id: 20, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
    {_id: 21, x: [[{y: {z: 1}}, {y: 2}], {y: 3}, {y: {z: 2}}, [[[{y: 5}, {y: {z: 3}}]]], {y: 6}]},
    {
        _id: 22,
        tf: [true, false],
        ff: [false, false],
        t: true,
        f: false,
        n: null,
        a: 1,
        b: 0,
        zero: 0,
    },
    {_id: 23, i1: NumberInt(1), i2: NumberInt(-1), i3: NumberInt(-2147483648)},
    {_id: 24, l1: NumberLong("12345678900"), l2: NumberLong("-12345678900")},
    {_id: 25, s: "string", l: NumberLong("-9223372036854775808"), n: null},
    {_id: 26, d1: 4.6, d2: -4.6, dec1: NumberDecimal("4.6"), dec2: NumberDecimal("-4.6")},
    {_id: 27, dec1: NumberDecimal("-1.0"), dec2: NumberDecimal("2.0"), dec3: NumberDecimal("3.0")},
];
assert.commandWorked(coll.insert(documents));

// A concise way to express an "expected" result that takes the form
// [{_id: 0, foo: <BOOL>}, {_id, 1, foo: <BOOL>}, ..., {_id: 26, foo: <BOOL>}] by passing an object
// of the form {foo: [INTEGER_LIST]}, where INTEGER_LIST is the list of '_id' values for documents
// where "foo" should be true.
function computedProjectionWithBoolValues(boolProj) {
    return documents.map((doc) => {
        const projectedDoc = {_id: doc._id};
        Object.keys(boolProj).forEach((key) => {
            projectedDoc[key] = boolProj[key].includes(doc._id);
        });
        return projectedDoc;
    });
}

const testCases = [
    {
        desc: "Single-level path 1",
        expected: [
            {_id: 0, foo: 1},
            {_id: 1, foo: 2},
            {_id: 2, foo: 3},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15, foo: 10},
            {_id: 16, foo: 10},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 1},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {_id: 1, foo: "$a"},
    },
    {
        desc: "Single-level path 2",
        expected: [
            {_id: 0, foo: 1},
            {_id: 1, foo: 2},
            {_id: 2, foo: 3},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15, foo: 10},
            {_id: 16, foo: 10},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 1},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: "$a"},
    },
    {
        desc: "Single-level path 3",
        expected: [
            {foo: 1},
            {foo: 2},
            {foo: 3},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {foo: 10},
            {foo: 10},
            {},
            {},
            {},
            {},
            {},
            {foo: 1},
            {},
            {},
            {},
            {},
            {},
        ],
        query: {},
        proj: {_id: 0, foo: "$a"},
    },
    {
        desc: "Two single-level paths",
        expected: [
            {_id: 0, foo: 1, bar: "x"},
            {_id: 1, foo: 2, bar: "y"},
            {_id: 2, foo: 3, bar: "z"},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15, foo: 10},
            {_id: 16, foo: 10},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 1, bar: 0},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {_id: 1, foo: "$a", bar: "$b"},
    },
    {
        desc: "Simple addition",
        expected: [
            {_id: 0, foo: 11},
            {_id: 1, foo: 13},
            {_id: 2, foo: 15},
        ],
        query: {c: {$gt: 0}},
        proj: {foo: {$add: ["$a", "$c"]}},
    },
    {
        desc: "$add with arity 1 and 3",
        expected: [{_id: 27, add1: NumberDecimal("-1.0"), add3: NumberDecimal("4.0")}],
        query: {dec1: NumberDecimal("-1.0")},
        proj: {add1: {$add: "$dec1"}, add3: {$add: ["$dec1", "$dec2", "$dec3"]}},
    },
    {
        desc: "Single-level path 4",
        expected: [
            {_id: 0, a: 1, foo: "x"},
            {_id: 1, a: 2, foo: "y"},
            {_id: 2, a: 3, foo: "z"},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15, a: 10},
            {_id: 16, a: 10},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, a: 1, foo: 0},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {a: 1, foo: "$b"},
    },
    {
        desc: "Two-level path 1",
        expected: [
            {_id: 0},
            {_id: 1},
            {_id: 2},
            {_id: 3, foo: 1},
            {_id: 4, foo: 2},
            {_id: 5, foo: [1, 2, 3]},
            {_id: 6, foo: 4},
            {_id: 7, foo: [1]},
            {_id: 8, foo: [1, 2]},
            {_id: 9, foo: [1, [1, 2, 3]]},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17, foo: [{z: 1}, {z: 2}]},
            {_id: 18, foo: [3, 4, 6]},
            {_id: 19},
            {_id: 20, foo: [3, 4, 6]},
            {_id: 21, foo: [3, {z: 2}, 6]},
            {_id: 22},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {_id: 1, foo: "$x.y"},
    },
    {
        desc: "Two-level path 2",
        expected: [
            {_id: 0},
            {_id: 1},
            {_id: 2},
            {_id: 3, foo: 1},
            {_id: 4, foo: 2},
            {_id: 5, foo: [1, 2, 3]},
            {_id: 6, foo: 4},
            {_id: 7, foo: [1]},
            {_id: 8, foo: [1, 2]},
            {_id: 9, foo: [1, [1, 2, 3]]},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17, foo: [{z: 1}, {z: 2}]},
            {_id: 18, foo: [3, 4, 6]},
            {_id: 19},
            {_id: 20, foo: [3, 4, 6]},
            {_id: 21, foo: [3, {z: 2}, 6]},
            {_id: 22},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: "$x.y"},
    },
    {
        desc: "Two-level path 3",
        expected: [
            {},
            {},
            {},
            {foo: 1},
            {foo: 2},
            {foo: [1, 2, 3]},
            {foo: 4},
            {foo: [1]},
            {foo: [1, 2]},
            {foo: [1, [1, 2, 3]]},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {foo: [{z: 1}, {z: 2}]},
            {foo: [3, 4, 6]},
            {},
            {foo: [3, 4, 6]},
            {foo: [3, {z: 2}, 6]},
            {},
            {},
            {},
            {},
            {},
            {},
        ],
        query: {},
        proj: {_id: 0, foo: "$x.y"},
    },
    {
        desc: "Two two-level paths",
        expected: [
            {_id: 0},
            {_id: 1},
            {_id: 2},
            {_id: 3, foo: 1},
            {_id: 4, foo: 2},
            {_id: 5, foo: [1, 2, 3], bar: [4, 5, 6]},
            {_id: 6, foo: 4, bar: 4},
            {_id: 7, foo: [1], bar: [1]},
            {_id: 8, foo: [1, 2], bar: [5, 6]},
            {_id: 9, foo: [1, [1, 2, 3]], bar: [4, [4, 5, 6]]},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17, foo: [{z: 1}, {z: 2}]},
            {_id: 18, foo: [3, 4, 6]},
            {_id: 19},
            {_id: 20, foo: [3, 4, 6]},
            {_id: 21, foo: [3, {z: 2}, 6]},
            {_id: 22},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: "$x.y", bar: "$v.w"},
    },
    {
        desc: "Addition of two-level paths",
        expected: [{_id: 19, foo: 15}],
        query: {"i.j": {$gt: 0}},
        proj: {foo: {$add: ["$i.j", "$k.l"]}},
    },
    {
        desc: "Dotted-path projection and two-level path",
        expected: [
            {_id: 0},
            {_id: 1},
            {_id: 2},
            {_id: 3, x: {y: 1}},
            {_id: 4, x: {y: 2}},
            {_id: 5, x: {y: [1, 2, 3]}, foo: [4, 5, 6]},
            {_id: 6, x: {y: 4}, foo: 4},
            {_id: 7, x: [{y: 1}], foo: [1]},
            {_id: 8, x: [{y: 1}, {y: 2}], foo: [5, 6]},
            {_id: 9, x: [{y: 1}, {y: [1, 2, 3]}], foo: [4, [4, 5, 6]]},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17, x: {y: [{z: 1}, {z: 2}]}},
            {_id: 18, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
            {_id: 19},
            {_id: 20, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
            {
                _id: 21,
                x: [[{y: {z: 1}}, {y: 2}], {y: 3}, {y: {z: 2}}, [[[{y: 5}, {y: {z: 3}}]]], {y: 6}],
            },
            {_id: 22},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {"x.y": 1, foo: "$v.w"},
    },
    //
    // Test simple expressions with the $abs operator.
    //
    {
        desc: "$abs operator",
        expected: [{_id: 23, abs_i1: 1, abs_i2: 1, abs_i3: NumberLong("2147483648")}],
        query: {i1: 1},
        proj: {abs_i1: {$abs: "$i1"}, abs_i2: {$abs: "$i2"}, abs_i3: {$abs: "$i3"}},
    },
    {
        desc: "$abs with NumberLong input",
        expected: [{_id: 24, abs_l1: NumberLong("12345678900"), abs_l2: NumberLong("12345678900")}],
        query: {l1: NumberLong("12345678900")},
        proj: {abs_l1: {$abs: "$l1"}, abs_l2: {$abs: "$l2"}},
    },
    {
        desc: "$abs with NumberDecimal input",
        expected: [
            {
                _id: 26,
                abs_d1: 4.6,
                abs_d2: 4.6,
                abs_dec1: NumberDecimal("4.6"),
                abs_dec2: NumberDecimal("4.6"),
            },
        ],
        query: {d1: 4.6},
        proj: {
            abs_d1: {$abs: "$d1"},
            abs_d2: {$abs: "$d2"},
            abs_dec1: {$abs: "$dec1"},
            abs_dec2: {$abs: "$dec2"},
        },
    },
    {
        desc: "$abs with string input",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {abs_s: {$abs: "$s"}},
    },
    {
        desc: "$abs with MIN_LONG_LONG input",
        expectedErrorCode: 28680,
        query: {s: "string"},
        proj: {abs_l: {$abs: "$l"}},
    },
    {
        desc: "$abs with missing input",
        expected: [{_id: 25, abs_n: null, abs_ne: null}],
        query: {s: "string"},
        proj: {abs_n: {$abs: "$n"}, abs_ne: {$abs: "$non_existent"}},
    },
    //
    // Test $and/$or.
    //
    {
        desc: "Single-branch $and",
        expected: [
            {_id: 0, foo: true},
            {_id: 1, foo: true},
            {_id: 2, foo: true},
            {_id: 3, foo: false},
            {_id: 4, foo: false},
            {_id: 5, foo: false},
            {_id: 6, foo: false},
            {_id: 7, foo: false},
            {_id: 8, foo: false},
            {_id: 9, foo: false},
            {_id: 10, foo: false},
            {_id: 11, foo: false},
            {_id: 12, foo: false},
            {_id: 13, foo: false},
            {_id: 14, foo: false},
            {_id: 15, foo: true},
            {_id: 16, foo: true},
            {_id: 17, foo: false},
            {_id: 18, foo: false},
            {_id: 19, foo: false},
            {_id: 20, foo: false},
            {_id: 21, foo: false},
            {_id: 22, foo: true},
            {_id: 23, foo: false},
            {_id: 24, foo: false},
            {_id: 25, foo: false},
            {_id: 26, foo: false},
            {_id: 27, foo: false},
        ],
        query: {},
        proj: {foo: {$and: ["$a"]}},
    },
    {
        desc: "Single-branch $or",
        expected: [
            {_id: 0, foo: true},
            {_id: 1, foo: true},
            {_id: 2, foo: true},
            {_id: 3, foo: false},
            {_id: 4, foo: false},
            {_id: 5, foo: false},
            {_id: 6, foo: false},
            {_id: 7, foo: false},
            {_id: 8, foo: false},
            {_id: 9, foo: false},
            {_id: 10, foo: false},
            {_id: 11, foo: false},
            {_id: 12, foo: false},
            {_id: 13, foo: false},
            {_id: 14, foo: false},
            {_id: 15, foo: true},
            {_id: 16, foo: true},
            {_id: 17, foo: false},
            {_id: 18, foo: false},
            {_id: 19, foo: false},
            {_id: 20, foo: false},
            {_id: 21, foo: false},
            {_id: 22, foo: true},
            {_id: 23, foo: false},
            {_id: 24, foo: false},
            {_id: 25, foo: false},
            {_id: 26, foo: false},
            {_id: 27, foo: false},
        ],
        query: {},
        proj: {foo: {$or: ["$a"]}},
    },
    {
        desc: "$and with BSONNull branch",
        expected: [{_id: 22, foo: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$and: ["$n"]}},
    },
    {
        desc: "$or with BSONNull branch",
        expected: [{_id: 22, foo: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$or: ["$n"]}},
    },
    {
        desc: "$and with missing path in branch",
        expected: [{_id: 22, foo: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$and: ["$nonexistent"]}},
    },
    {
        desc: "$or with missing path in branch",
        expected: [{_id: 22, foo: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$or: ["$nonexistent"]}},
    },
    {
        desc: "$and with array branch",
        expected: [{_id: 22, foo: true, bar: true}],
        query: {_id: 22, a: 1},
        proj: {foo: {$and: []}, bar: {$and: ["$tf", "$t", "$a"]}},
    },
    {
        desc: "Three-branch $or",
        expected: [{_id: 22, foo: false, bar: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$or: []}, bar: {$or: ["$f", "$n", "$nonexistent"]}},
    },
    {
        desc: "Multiple computed $and projections 1",
        expected: [{_id: 22, foo: false, bar: false, baz: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$and: ["$a", "$b"]}, bar: {$and: ["$a", "$f"]}, baz: {$and: ["$a", "$n"]}},
    },
    {
        desc: "Multiple computed $or projections",
        expected: [{_id: 22, foo: true, bar: true, baz: true}],
        query: {_id: 22, a: 1},
        proj: {foo: {$or: ["$a", "$b"]}, bar: {$or: ["$a", "$f"]}, baz: {$or: ["$a", "$n"]}},
    },
    {
        desc: "Multiple computed $and projections 2",
        expected: [{_id: 22, foo: true, bar: false}],
        query: {_id: 22, a: 1},
        proj: {foo: {$and: ["$ff", "$t"]}, bar: {$and: ["$nonexistent", "$t"]}},
    },
    //
    // $switch and $cond tests.
    //
    {
        desc: "Single-case $switch with default",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 1, foo: 11},
            {_id: 2, foo: 12},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 0},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: {$switch: {branches: [{case: {$eq: ["$a", 1]}, then: "$b"}], default: "$c"}}},
    },
    {
        desc: "$cond",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 1, foo: 11},
            {_id: 2, foo: 12},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 0},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: {$cond: {if: {$eq: ["$a", 1]}, then: "$b", else: "$c"}}},
    },
    {
        desc: "Two-case $switch with default",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 1, foo: 2},
            {_id: 2, foo: 12},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 0},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {
            foo: {
                $switch: {
                    branches: [
                        {case: {$eq: ["$a", 1]}, then: "$b"},
                        {case: {$eq: ["$b", "y"]}, then: "$a"},
                    ],
                    default: "$c",
                },
            },
        },
    },
    //
    // Failing expressions
    //
    {
        desc: "$abs with string input as $and branch",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$and: [{$abs: ["$s"]}, "$n"]}},
    },
    {
        desc: "$abs with string input as $or branch",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$or: [{$abs: ["$s"]}, "$s"]}},
    },
    {
        desc: "Switch fall-through with no default",
        expectedErrorCode: 40066,
        query: {},
        proj: {
            foo: {
                $switch: {
                    branches: [
                        {case: {$eq: ["$a", 1]}, then: "$b"},
                        {case: {$eq: ["$b", "y"]}, then: "$a"},
                    ],
                    // No default case.
                },
            },
        },
    },
    {
        desc: "$abs with string input as case condition",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$switch: {branches: [{case: {$gt: [{$abs: ["$s"]}, 0]}, then: "$n"}]}}},
    },
    {
        desc: "$abs with string input as case result",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$switch: {branches: [{case: {$eq: ["$s", "string"]}, then: {$abs: ["$s"]}}]}}},
    },
    {
        desc: "$abs with string input as $switch default",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {
            foo: {$switch: {branches: [{case: {$eq: ["$s", 0]}, then: "$n"}], default: {$abs: "$s"}}},
        },
    },
    {
        desc: "$abs with string input as $cond condition",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$cond: {if: {$eq: [{$abs: "$s"}, "$n"]}, then: "$b", else: "$c"}}},
    },
    {
        desc: "$abs with string input as $cond result (then)",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$cond: {if: {$eq: ["$s", "string"]}, then: {$abs: ["$s"]}, else: "$c"}}},
    },
    {
        desc: "$abs with string input as $cond result (else)",
        expectedErrorCode: 28765,
        query: {s: "string"},
        proj: {foo: {$cond: {if: {$eq: ["$s", "gnirts"]}, then: "$b", else: {$abs: ["$s"]}}}},
    },
    //
    // Test short circuiting: these queries have expressions that would fail (because |$x| is
    // invalid) but won't because they do not execute.
    //
    {
        desc: "$abs with string input as short-circuited $and branch",
        expected: [{_id: 25, foo: false}],
        query: {s: "string"},
        proj: {foo: {$and: ["$n", {$abs: ["$s"]}]}},
    },
    {
        desc: "$abs with string input as short-circuited $or branch",
        expected: [{_id: 25, foo: true}],
        query: {s: "string"},
        proj: {foo: {$or: ["$s", {$abs: ["$s"]}]}},
    },
    //
    // Test that short-circuited branches do not do any work, such as traversing an array. The
    // short-circuited branches cointain divide by zero operation which if it were to execute
    // would result in an error from the query that would cause the test to fail.
    //
    {
        desc: "$divide by zero in short-circuited $and/$or branches",
        expected: [{_id: 22, foo: false, bar: true}],
        query: {_id: 22, a: 1},
        proj: {
            foo: {$and: ["$f", {$divide: [1, "$zero"]}]},
            bar: {$or: ["$t", {$divide: [1, "$zero"]}]},
        },
    },
    {
        desc: "$divide by zero in nested short-circuited $or branches",
        expected: [{_id: 22, foo: false, bar: true}],
        query: {_id: 22, a: 1},
        proj: {
            foo: {$and: ["$f", {$or: ["$f", {$divide: [1, "$zero"]}]}, {$eq: ["$a", 1]}]},
            bar: {$and: ["$t", {$or: ["$t", {$divide: [1, "$zero"]}]}, {$eq: ["$a", 1]}]},
        },
    },
    {
        desc: "$divide by zero in untaken $switch cases",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 22, foo: 0},
        ],
        query: {a: 1},
        proj: {
            foo: {
                $switch: {
                    branches: [
                        {case: {$eq: ["$a", 2]}, then: {$divide: [1, "$zero"]}},
                        {case: {$eq: ["$a", 3]}, then: {$divide: [1, "$zero"]}},
                    ],
                    default: "$b",
                },
            },
        },
    },
    {
        desc: "$divide by zero in unevaluated case condition and untaken $switch default branch",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 22, foo: 0},
        ],
        query: {a: 1},
        proj: {
            foo: {
                $switch: {
                    branches: [
                        {case: {$eq: ["$a", 1]}, then: "$b"},
                        {case: {$divide: [1, "$zero"]}, then: {$divide: [1, "$zero"]}},
                    ],
                    default: {$divide: [1, "$zero"]},
                },
            },
        },
    },
    {
        desc: "$divide by zero in untaken $cond branch (else)",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 22, foo: 0},
        ],
        query: {a: 1},
        proj: {foo: {$cond: {if: {$eq: ["$a", 1]}, then: "$b", else: {$divide: [1, "$zero"]}}}},
    },
    {
        desc: "$divide by zero in untaken $cond branch (then)",
        expected: [
            {_id: 0, foo: "x"},
            {_id: 22, foo: 0},
        ],
        query: {a: 1},
        proj: {foo: {$cond: {if: {$eq: ["$a", 2]}, then: {$divide: [1, "$zero"]}, else: "$b"}}},
    },
    //
    // $let tests.
    //
    {
        desc: "$let with single-path variable definitions 1",
        expected: computedProjectionWithBoolValues({foo: [0, 1, 2, 15, 16, 22]}),
        query: {},
        proj: {foo: {$let: {vars: {va: "$a", vb: "$b"}, "in": {$and: "$$va"}}}},
    },
    {
        desc: "$let with single-path variable definitions 2",
        expected: computedProjectionWithBoolValues({foo: [0, 1, 2]}),
        query: {},
        proj: {foo: {$let: {vars: {va: "$a", vb: "$b"}, "in": {$and: "$$vb"}}}},
    },
    {
        desc: "$let with single-path variable definitions 3",
        expected: computedProjectionWithBoolValues({foo: [0, 1, 2]}),
        query: {},
        proj: {foo: {$let: {vars: {va: "$a", vb: "$b"}, "in": {$and: ["$$va", "$$vb"]}}}},
    },
    {
        desc: "$let with $and variable definition",
        expected: computedProjectionWithBoolValues({foo: [0, 1, 2]}),
        query: {},
        proj: {foo: {$let: {vars: {va: {$and: ["$a", "$b"]}}, "in": "$$va"}}},
    },
    {
        desc: "Two-level path including $let variable",
        expected: [
            {_id: 0},
            {_id: 1},
            {_id: 2},
            {_id: 3, foo: 1},
            {_id: 4, foo: 2},
            {_id: 5, foo: [1, 2, 3]},
            {_id: 6, foo: 4},
            {_id: 7, foo: [1]},
            {_id: 8, foo: [1, 2]},
            {_id: 9, foo: [1, [1, 2, 3]]},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15},
            {_id: 16},
            {_id: 17, foo: [{z: 1}, {z: 2}]},
            {_id: 18, foo: [3, 4, 6]},
            {_id: 19},
            {_id: 20, foo: [3, 4, 6]},
            {_id: 21, foo: [3, {z: 2}, 6]},
            {_id: 22},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: {$let: {vars: {va: "$x"}, "in": "$$va.y"}}},
    },
    {
        desc: "Nested $let expressions",
        expected: computedProjectionWithBoolValues({foo: [22]}),
        query: {},
        proj: {
            foo: {
                $let: {
                    vars: {vt: "$t", vf: "$f"},
                    "in": {
                        $let: {vars: {vf: "$$vt", va: "$a"}, "in": {$and: ["$$vt", "$$vf", "$$va"]}},
                    },
                },
            },
        },
    },
    {
        desc: "$let with variable definition including nested $let",
        expected: [{_id: 22, foo: false}],
        query: {_id: 22, a: 1},
        proj: {
            foo: {
                $let: {
                    vars: {va: {$let: {vars: {vt: "$t", va: "$va"}, "in": {$and: ["$$vt", "$$va"]}}}},
                    "in": "$$va",
                },
            },
        },
    },
    {
        desc: "$let renaming $$CURRENT",
        expected: [
            {_id: 0, foo: 1},
            {_id: 1, foo: 2},
            {_id: 2, foo: 3},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15, foo: 10},
            {_id: 16, foo: 10},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 1},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: {$let: {vars: {doc: "$$CURRENT"}, "in": "$$doc.a"}}},
    },
    {
        desc: "$let shadowing $$CURRENT",
        expected: [
            {_id: 0, foo: 1},
            {_id: 1, foo: 2},
            {_id: 2, foo: 3},
            {_id: 3},
            {_id: 4},
            {_id: 5},
            {_id: 6},
            {_id: 7},
            {_id: 8},
            {_id: 9},
            {_id: 10},
            {_id: 11},
            {_id: 12},
            {_id: 13},
            {_id: 14},
            {_id: 15, foo: 10},
            {_id: 16, foo: 10},
            {_id: 17},
            {_id: 18},
            {_id: 19},
            {_id: 20},
            {_id: 21},
            {_id: 22, foo: 1},
            {_id: 23},
            {_id: 24},
            {_id: 25},
            {_id: 26},
            {_id: 27},
        ],
        query: {},
        proj: {foo: {$let: {vars: {CURRENT: "$$CURRENT.a"}, "in": "$$CURRENT"}}},
    },
    {
        desc: "$$REMOVE",
        expected: documents.map((doc) => ({_id: doc._id})),
        query: {},
        proj: {a: "$$REMOVE"},
    },
    {
        desc: "$$REMOVE with additional path components",
        expected: documents.map((doc) => ({_id: doc._id})),
        query: {},
        proj: {a: "$$REMOVE.x.y"},
    },
    {
        desc: "$lt",
        expected: computedProjectionWithBoolValues({
            foo: [3, 4, 5, 6, 7, 8, 9, 17, 18, 20, 21],
            bar: [0, 1, 2],
            baz: [5, 8, 9],
            qux: [],
        }),
        query: {},
        proj: {
            foo: {$lt: ["$a", "$x"]},
            bar: {$lt: ["$a", "$b"]},
            baz: {$lt: ["$x.y", "$v.w"]},
            qux: {$lt: ["$a", "$nonexistent"]},
        },
    },
    {
        desc: "$lte",
        expected: computedProjectionWithBoolValues({
            foo: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27],
            bar: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27],
            baz: [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27],
            qux: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27],
        }),
        query: {},
        proj: {
            foo: {$lte: ["$a", "$x"]},
            bar: {$lte: ["$a", "$b"]},
            baz: {$lte: ["$x.y", "$v.w"]},
            qux: {$lte: ["$a", "$nonexistent"]},
        },
    },
    {
        desc: "$gt",
        expected: computedProjectionWithBoolValues({
            foo: [0, 1, 2, 15, 22],
            bar: [15, 16, 22],
            baz: [3, 4, 17, 18, 20, 21],
            qux: [0, 1, 2, 15, 16, 22],
        }),
        query: {},
        proj: {
            foo: {$gt: ["$a", "$x"]},
            bar: {$gt: ["$a", "$b"]},
            baz: {$gt: ["$x.y", "$v.w"]},
            qux: {$gt: ["$a", "$nonexistent"]},
        },
    },
    {
        desc: "$gte",
        expected: computedProjectionWithBoolValues({
            foo: [0, 1, 2, 10, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27],
            bar: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],
            baz: [0, 1, 2, 3, 4, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],
            qux: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],
        }),
        query: {},
        proj: {
            foo: {$gte: ["$a", "$x"]},
            bar: {$gte: ["$a", "$b"]},
            baz: {$gte: ["$x.y", "$v.w"]},
            qux: {$gte: ["$a", "$nonexistent"]},
        },
    },
    {
        desc: "$eq",
        expected: computedProjectionWithBoolValues({
            foo: [10, 11, 12, 13, 14, 16, 19, 23, 24, 25, 26, 27],
            bar: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27],
            baz: [0, 1, 2, 6, 7, 10, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27],
            qux: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27],
        }),
        query: {},
        proj: {
            foo: {$eq: ["$a", "$x"]},
            bar: {$eq: ["$a", "$b"]},
            baz: {$eq: ["$x.y", "$v.w"]},
            qux: {$eq: ["$a", "$nonexistent"]},
        },
    },
    {
        desc: "$ne",
        expected: computedProjectionWithBoolValues({
            foo: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 17, 18, 20, 21, 22],
            bar: [0, 1, 2, 15, 16, 22],
            baz: [3, 4, 5, 8, 9, 17, 18, 20, 21],
            qux: [0, 1, 2, 15, 16, 22],
        }),
        query: {},
        proj: {
            foo: {$ne: ["$a", "$x"]},
            bar: {$ne: ["$a", "$b"]},
            baz: {$ne: ["$x.y", "$v.w"]},
            qux: {$ne: ["$a", "$nonexistent"]},
        },
    },
    {
        desc: "$cmp",
        expected: [
            {_id: 0, foo: 1, bar: -1, baz: 0, qux: 1},
            {_id: 1, foo: 1, bar: -1, baz: 0, qux: 1},
            {_id: 2, foo: 1, bar: -1, baz: 0, qux: 1},
            {_id: 3, foo: -1, bar: 0, baz: 1, qux: 0},
            {_id: 4, foo: -1, bar: 0, baz: 1, qux: 0},
            {_id: 5, foo: -1, bar: 0, baz: -1, qux: 0},
            {_id: 6, foo: -1, bar: 0, baz: 0, qux: 0},
            {_id: 7, foo: -1, bar: 0, baz: 0, qux: 0},
            {_id: 8, foo: -1, bar: 0, baz: -1, qux: 0},
            {_id: 9, foo: -1, bar: 0, baz: -1, qux: 0},
            {_id: 10, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 11, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 12, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 13, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 14, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 15, foo: 1, bar: 1, baz: 0, qux: 1},
            {_id: 16, foo: 0, bar: 1, baz: 0, qux: 1},
            {_id: 17, foo: -1, bar: 0, baz: 1, qux: 0},
            {_id: 18, foo: -1, bar: 0, baz: 1, qux: 0},
            {_id: 19, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 20, foo: -1, bar: 0, baz: 1, qux: 0},
            {_id: 21, foo: -1, bar: 0, baz: 1, qux: 0},
            {_id: 22, foo: 1, bar: 1, baz: 0, qux: 1},
            {_id: 23, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 24, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 25, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 26, foo: 0, bar: 0, baz: 0, qux: 0},
            {_id: 27, foo: 0, bar: 0, baz: 0, qux: 0},
        ],
        query: {},
        proj: {
            foo: {$cmp: ["$a", "$x"]},
            bar: {$cmp: ["$a", "$b"]},
            baz: {$cmp: ["$x.y", "$v.w"]},
            qux: {$cmp: ["$a", "$nonexistent"]},
        },
    },
    //
    // Nesting torture tests.
    //
    {
        desc: "Nesting torture test 1",
        expected: computedProjectionWithBoolValues({foo: [5, 6, 7, 8, 9]}),
        query: {},
        proj: {
            foo: {
                $let: {
                    vars: {v1: {$or: ["$x.y", "$v.w"]}, vx: "$x"},
                    "in": {$and: ["$$vx.y", "$v.w"]},
                },
            },
        },
    },
    {
        desc: "Nesting torture test 2",
        expected: computedProjectionWithBoolValues({foo: [5, 6, 7, 8, 9]}),
        query: {},
        proj: {
            foo: {
                $let: {
                    vars: {v1: "$x.y"},
                    "in": {
                        $let: {
                            vars: {v2: {$let: {vars: {v3: "$v.w"}, "in": {$and: ["$$v1", "$$v3"]}}}},
                            "in": "$$v2",
                        },
                    },
                },
            },
        },
    },
    {
        desc: "Nesting torture test 3",
        expected: computedProjectionWithBoolValues({foo: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 18, 20, 21, 22]}),
        query: {},
        proj: {foo: {$or: ["$a", {$lt: [{$and: ["$a", "$c"]}, {$or: ["$c", "$x"]}]}, "$x"]}},
    },
    {
        desc: "Ludicrous nesting torture test with multiple computed fields",
        expected: [
            {_id: 0, foo: false, baz: false},
            {_id: 1, foo: false, baz: false},
            {_id: 2, foo: false, baz: false},
            {_id: 3, foo: 2, baz: false},
            {_id: 4, bar: 2, baz: false},
            {_id: 5, foo: true, bar: [1, 2, 3], baz: true},
            {_id: 6, foo: true, bar: 4, baz: false},
            {_id: 7, foo: true, bar: [1], baz: false},
            {_id: 8, foo: true, bar: [1, 2], baz: true},
            {_id: 9, foo: true, bar: [1, [1, 2, 3]], baz: true},
            {_id: 10, foo: false, baz: false},
            {_id: 11, foo: false, baz: false},
            {_id: 12, foo: false, baz: false},
            {_id: 13, foo: false, baz: false},
            {_id: 14, foo: false, baz: false},
            {_id: 15, foo: false, baz: false},
            {_id: 16, foo: false, baz: false},
            {_id: 17, foo: true, bar: [{z: 1}, {z: 2}], baz: false},
            {_id: 18, foo: true, bar: [3, 4, 6], baz: false},
            {_id: 19, foo: false, baz: false},
            {_id: 20, foo: true, bar: [3, 4, 6], baz: false},
            {_id: 21, foo: true, bar: [3, {z: 2}, 6], baz: false},
            {_id: 22, foo: true, baz: false},
            {_id: 23, foo: false, baz: false},
            {_id: 24, foo: false, baz: false},
            {_id: 25, foo: false, baz: false},
            {_id: 26, foo: false, baz: false},
            {_id: 27, foo: false, baz: false},
        ],
        query: {},
        proj: {
            foo: {
                $let: {
                    vars: {
                        v1: {$or: ["$x.y", "$v.w"]},
                        v2: {$switch: {branches: [{case: "$v.w", then: 1}], default: 2}},
                        v3: "$b",
                    },
                    "in": {
                        $switch: {
                            branches: [
                                {case: {$eq: ["$x.y", 1]}, then: "$$v2"},
                                {case: {$eq: ["$x.y", 2]}, then: "$v.w"},
                                {case: {$eq: ["$$v3", 2]}, then: "$c"},
                            ],
                            default: {$or: ["$$v1", "$tf"]},
                        },
                    },
                },
            },
            bar: {
                $switch: {
                    branches: [
                        {case: {$gt: ["$x.y", 1]}, then: "$x.y"},
                        {case: {$let: {vars: {v4: "$v.w1"}, "in": "$$v4"}}, then: "$v.w2"},
                    ],
                    default: "$x.y.z",
                },
            },
            baz: {
                $let: {
                    vars: {v5: "$x.y", v6: "$v.w"},
                    "in": {
                        $cond: {
                            if: {$lt: ["$$v5", "$$v6"]},
                            then: {
                                $switch: {
                                    branches: [
                                        {case: {$eq: ["$v.w", 5]}, then: "$v.w"},
                                        {case: {$eq: ["$v.w", 4]}, then: "$x.y"},
                                    ],
                                    default: {$or: ["$$v5", "$$v6"]},
                                },
                            },
                            else: false,
                        },
                    },
                },
            },
        },
    },
];

testCases.forEach(function (test) {
    if (test.expected) {
        let actual;
        assert.doesNotThrow(
            () => {
                actual = coll.find(test.query, test.proj).toArray();
            },
            [],
            test,
        );
        assert(arrayEq(actual, test.expected), Object.assign({actual: actual}, test));
    } else {
        assert(test.expectedErrorCode, test);
        let result;
        assert.throwsWithCode(
            () => (result = coll.find(test.query, test.proj).toArray()),
            test.expectedErrorCode,
            [],
            Object.assign({result: result}, test),
        );
    }
});
